/*
* Copyright (C) 2020 ~ 2024 chaigec All Rights Reserved.
*
* Author:     chaigec <chaigec@163.com>
* Maintainer: chaigec <chaigec@163.com>

 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
*/

#include "MkHttpSession.h"
#include "MkHttpServer.h"
#include "MkWebSocketMessage.h"
#include "MkHttpResponse.h"
#include "MkEvent/MkEvent.h"
#include "MkEvent/MkTimer.h"
#include "MkUtil/MkLog.h"
#include "MkUtil/MkUtil.h"
#include "MkUtil/MkBase64.h"
#include "MkUtil/MkSha1.h"

CMkHttpSession::CMkHttpSession(CMkEvent *pEvent, CMkTimer* pTimer, CMkHttpServer* parent)
    : CMkTcpSession(pEvent, pTimer, parent)
    , m_bWebSocket(FALSE)
    , m_CurHttpRequests(0)
    , m_bSendPing(FALSE)
    , m_pReplyFp(nullptr)
    , m_nLen(Len20K)
    , m_nOffset(0)
    , m_pParent(parent)
{
    m_pRecvBuffer = (Uint8 *)malloc(m_nLen);
}

Uint32 CMkHttpSession::OnRecv()
{
    Uint32 RecvLen = m_nLen - m_nOffset;
    if (0 == RecvLen) {
        MkErrorLog("buffer size:%d not parse success\n", m_nLen);
        Close();
        return UnknownError;
    }
    Uint32 ErrorCode = Recv(m_pRecvBuffer + m_nOffset, RecvLen);
    MkCheckErrorLog(ErrorCode, "http session recv failed\n");
    m_nOffset += RecvLen;
    Uint32 nTotalUsed = 0;
    while (nTotalUsed < m_nOffset) {
        Uint32 nUsed = 0;
        if (m_bWebSocket) {
            ErrorCode = WebSocketRecvParse(m_pRecvBuffer, m_nOffset, nUsed);
        } else {
            ErrorCode = HttpRecvParse(m_pRecvBuffer, m_nOffset, nUsed);
        }
        nTotalUsed += nUsed;
        if (SocketClosed == ErrorCode) {
            return NoneError;
        }
        if (NoneError != ErrorCode) {
            break;
        }
    }
    if (m_nOffset != nTotalUsed) {
        memmove(m_pRecvBuffer, m_pRecvBuffer + nTotalUsed, m_nOffset - nTotalUsed);
    }
    m_nOffset -= nTotalUsed;
    return NoneError;
}

Uint32 CMkHttpSession::OnWrite()
{
    CMkTcpSession::OnWrite();
    Uint32 ErrorCode = NoneError;
    Uint8 pBuffer[MtuSize * 10];
    while (m_pReplyFp) {
        int nRet = fread(pBuffer, 1, MtuSize * 10, m_pReplyFp);
        if (nRet <= 0) {
            fclose(m_pReplyFp);
            m_pReplyFp = nullptr;
            break;
        }
        ErrorCode = CMkTcpSession::Send(pBuffer, nRet);
        if (NoneError != ErrorCode) {
            break;
        }
    }
    return NoneError;
}

Uint32 CMkHttpSession::SendReply(const MkHttpStatusCode& Error, const MkHttpBodyType& BodyType/*=BodyTypeNone*/, const MkString& Body/* = ""*/)
{
    CMkHttpResponse Res;
    Res.SetCode(Error);
    Res.AddHeader("Content-Length", std::to_string(Body.size()));
    Res.AddHeader("Content-Type", CMkHttpUtil::GetInstance().GetBodyTypeDesc(BodyType));
    if (MkHttpStateBadRequest == Error) {
        Res.AddHeader("Connection", "close");
    } else if (MkHttpStateUnauthorized == Error) {
        Res.AddHeader("WWW-Authenticate", "BASIC realm=\"mk http server\"");
    }
    MkString GmtTime;
    NowMkTime.GetGMTString(GmtTime);
    Res.AddHeader("Data", GmtTime);
    Res.AddHeader("User-Agent", MkUserAgent);
    MkString Content;
    Uint32 ErrorCode = Res.ToString(Content);
    MkCheckErrorLog(ErrorCode, "packet http response failed\n");
    CMkTcpSession::Send((Uint8*)Content.c_str(), Content.size());
    if (!Body.empty()) {
        CMkTcpSession::Send((Uint8*)Body.c_str(), Body.size());
    }
    return NoneError;
}

Uint32 CMkHttpSession::SendFileReply(FILE* pFile, Uint64 FileSize)
{
    CMkHttpResponse Res;
    Res.SetCode(MkHttpStateOk);
    Res.AddHeader("Content-Length", std::to_string(FileSize));
    Res.AddHeader("Content-Type", CMkHttpUtil::GetInstance().GetBodyTypeDesc(MkHttpBodyTypeFormData));
    MkString GmtTime;
    NowMkTime.GetGMTString(GmtTime);
    Res.AddHeader("Data", GmtTime);
    Res.AddHeader("User-Agent", MkUserAgent);
    MkString Content;
    Uint32 ErrorCode = Res.ToString(Content);
    MkCheckErrorLog(ErrorCode, "packet http response failed\n");
    CMkTcpSession::Send((Uint8*)Content.c_str(), Content.size());

    m_pReplyFp = pFile;
    Uint8 pBuffer[MtuSize * 10];
    while (FileSize > 0) {
        int nRet = fread(pBuffer, 1, MtuSize * 10, m_pReplyFp);
        if (nRet <= 0) {
            fclose(m_pReplyFp);
            m_pReplyFp = nullptr;
            break;
        }
        ErrorCode = CMkTcpSession::Send(pBuffer, nRet);
        if (NoneError != ErrorCode) {
            break;
        }
    }
    return NoneError;
}

Uint32 CMkHttpSession::Check(const CMkTime& NowTime)
{
    if (m_bWebSocket 
        && m_bSendPing) {
        WebSocketSendPing();
    }
    CMkHttpServer *pServer = dynamic_cast<CMkHttpServer*>(GetParentServer());
    if (pServer && NowTime >= GetActiveTime() + pServer->GetKeepAliveSeconds() * 1000 * 1000) {
        MkWarningLog("http session has:%d seconds not active close it\n", pServer->GetKeepAliveSeconds());
        Close();
        return SocketClosed;
    }
    return NoneError;
}

Uint32 CMkHttpSession::SendWebsocketConnectResponse(const CMkHttpRequest& Request)
{
    MkString Upgrade;
    MkString Connection;
    MkString WsKey;
    Uint32 ErrorCode = Request.GetHeadValue("Upgrade", Upgrade);
    MkCheckErrorLog(ErrorCode, "get upgrade failed\n");

    ErrorCode = Request.GetHeadValue("Connection", Connection);
    MkCheckErrorLog(ErrorCode, "get connection failed\n");

    ErrorCode = Request.GetHeadValue("Sec-WebSocket-Key", WsKey);
    MkCheckErrorLog(ErrorCode, "get ws key failed\n");
    if ("websocket" != Upgrade
        || "Upgrade" != Connection
        || WsKey.empty()) {
        MkErrorLog("Upgrade:%s connect:%s wskey:%s\n", Upgrade.c_str(), Connection.c_str(), WsKey.c_str());
        return InvalidParams;
    }
    MkString WsAccept;
    MkString strBuf = WsKey + MkString(WsUuid);
    MkString Sha1Out;
    MkSha1Encode(strBuf, Sha1Out);
    MkBase64Encode(Sha1Out, WsAccept);
    MkString Protocol;
    Request.GetHeadValue("Sec-WebSocket-Protocol", Protocol);
    ErrorCode = SendWebSocketReply(WsAccept, Protocol);
    MkCheckErrorLog(ErrorCode, "send websocket reply failed\n");
    return NoneError;
}

Uint32 CMkHttpSession::SendWebSocketText(const MkString& Content, BOOL bMark/* = FALSE*/)
{
    CMkWebSocketMessage Message;
    MkString strHeader;
    Message.SetMark(bMark);
    Message.PacketHeader(Content.size(), strHeader);
    CMkBufferList SendList;
    SendList.Append(strHeader.c_str(), strHeader.size());
    SendList.Append((Uint8*)Content.c_str(), Content.size());
    CMkTcpSession::Send(SendList);
    return NoneError;
}

Uint32 CMkHttpSession::SendWebSocketBinary(const CMkBufferList& BufList, BOOL bMark/* = FALSE*/)
{
#if 1
    CMkWebSocketMessage Message;
    MkString strHeader;
    Message.SetFrameType(CMkWebSocketMessage::BinaryFrame);
    Message.SetMark(bMark);
    Message.PacketHeader(BufList.GetTatolLen(), strHeader);
    CMkBufferList SendList;
    SendList.Append(strHeader.c_str(), strHeader.size());
    SendList.Append(BufList);
    CMkTcpSession::Send(SendList);
#else
    MkList<CMkBuffer*>::const_iterator it = BufList.GetBufferList().begin();
    for (; it != BufList.GetBufferList().end(); it++) {
        CMkWebSocketMessage Message;
        MkString strHeader;
        Message.SetFrameType(CMkWebSocketMessage::BinaryFrame);
        Message.SetMark(bMark);
        Message.PacketHeader((*it)->GetLength(), strHeader);
        CMkBufferList SendList;
        SendList.Append(strHeader.c_str(), strHeader.size());
        SendList.Append(*(*it));
        CMkTcpSession::Send(SendList);
}
#endif
    return NoneError;
}

Uint32 CMkHttpSession::WebsocketSendClose()
{
    CMkWebSocketMessage Message;
    Message.SetFrameType(CMkWebSocketMessage::CloseFrame);
    MkString strHeader;
    Message.PacketHeader(0, strHeader);

    CMkTcpSession::Send((Uint8*)strHeader.c_str(), strHeader.size());
    return NoneError;
}

Uint32 CMkHttpSession::WebSocketRecvParse(const Uint8* pBuf, Uint32 nLen, Uint32& nUsed)
{
    CMkWebSocketMessage Message;
    Uint32 ErrorCode = Message.AddContent(m_pRecvBuffer, m_nOffset, nUsed);
    MkCheckErrorLog(ErrorCode, "websocket message add content failed\n");
    CMkWebSocketMessage::MkWebSocketFrameType FrameType = Message.Opcode();
    switch (FrameType) {
    case CMkWebSocketMessage::PingFrame:
        ErrorCode = WebSocketSendPong();
        break;
    case CMkWebSocketMessage::PongFrame:
        break;
    case CMkWebSocketMessage::CloseFrame:
    case CMkWebSocketMessage::ErrorFrame:
        WebsocketSendClose();
        ErrorCode = Close();
        return SocketClosed;
    case CMkWebSocketMessage::InCompleteFrame:
        m_WebSocketMsg += MkString(Message.GetPayloadData(), static_cast<Uint32>(Message.GetPayloadLength()));
        break;
    case CMkWebSocketMessage::TextFrame:
    case CMkWebSocketMessage::BinaryFrame:
        m_WebSocketMsg += MkString(Message.GetPayloadData(), static_cast<Uint32>(Message.GetPayloadLength()));
        if (m_pParent) {
            ErrorCode = m_pParent->OnWebsocketRecved(this, m_WebSocketMsg);
            if (NoneError != ErrorCode) {
                WebsocketSendClose();
                Close();
                return SocketClosed;
            }
        }
        m_WebSocketMsg.clear();
        break;
    default:
        break;
    }
    return ErrorCode;
}

Uint32 CMkHttpSession::HttpRecvParse(const Uint8* pBuf, Uint32 nLen, Uint32& nUsed)
{
    m_CurHttpRequests++;
    Uint32 ErrorCode = m_Request.Parse(m_pRecvBuffer, m_nOffset, nUsed);
    if (NotHasEnoughBuffer == ErrorCode) {
        ErrorCode = NoneError;
    } else if (NoneError != ErrorCode) {
        SendReply(MkHttpStateBadRequest);
        Close();
        ErrorCode = SocketClosed;
    } else {
        m_Request.SetSessionId(GetSessionId());
        ErrorCode = m_pParent->OnRecvRequest(this, m_Request);
        if (NoneError != ErrorCode
            || m_Request.IsConnectClose()) {
            Close();
            ErrorCode = SocketClosed;
        } else {
            m_Request.Release();
        }
    }
    MkCheckErrorLog(ErrorCode, "http recv parse failed\n");
    CMkHttpServer* pServer = dynamic_cast<CMkHttpServer*>(GetParentServer());
    if (pServer && m_CurHttpRequests >= pServer->GetMaxKeepAliveRequest()) {
        Close();
        ErrorCode = SocketClosed;
    }
    return ErrorCode;
}

Uint32 CMkHttpSession::SendWebSocketReply(const MkString& WsAccept, const MkString& Protocol)
{
    m_bWebSocket = TRUE;
    CMkHttpResponse Res;
    Res.SetCode(MkHttpStateSwitchProtocol);
    Res.AddHeader("Upgrade", "websocket");
    Res.AddHeader("Connection", "Upgrade");
    Res.AddHeader("Sec-WebSocket-Accept", WsAccept);
    if (!Protocol.empty()) {
        Res.AddHeader("Sec-WebSocket-Protocol", Protocol);
    }
    MkString GmtTime;
    NowMkTime.GetGMTString(GmtTime);
    Res.AddHeader("Data", GmtTime);
    Res.AddHeader("User-Agent", MkUserAgent);

    MkString Content;
    Uint32 ErrorCode = Res.ToString(Content);
    MkCheckErrorLog(ErrorCode, "packet http response failed\n");
    CMkTcpSession::Send((Uint8*)Content.c_str(), Content.size());
    return NoneError;
}

Uint32 CMkHttpSession::WebSocketSendPing()
{
    CMkWebSocketMessage Message;
    Message.SetFrameType(CMkWebSocketMessage::PingFrame);
    MkString strHeader;
    Message.PacketHeader(0, strHeader);

    CMkTcpSession::Send((Uint8*)strHeader.c_str(), strHeader.size());
    return NoneError;
}

Uint32 CMkHttpSession::WebSocketSendPong()
{
    CMkWebSocketMessage Message;
    Message.SetFrameType(CMkWebSocketMessage::PongFrame);
    MkString strHeader;
    Message.PacketHeader(0, strHeader);

    CMkTcpSession::Send((Uint8*)strHeader.c_str(), strHeader.size());
    return NoneError;
}

CMkHttpSession::~CMkHttpSession()
{
    MkFree(m_pRecvBuffer);
    m_bWebSocket = FALSE;
}